package log

import (
	"context"
	"fmt"
	"github.com/Unixeno/islands/cmd/connection"
	"github.com/Unixeno/islands/cmd/container"
	"github.com/Unixeno/islands/cmd/utils"
	"github.com/Unixeno/islands/pb"
	"github.com/spf13/cobra"
	"os"
	"sync"
)

var (
	follow     bool
	since      string
	tail       int32
	timestamps bool
	exec       bool
	stdin      bool
	stdout     bool
	stderr     bool
)

func GetLogCmd() *cobra.Command {
	logCmd := &cobra.Command{
		Use:   "logs [OPTIONS] CONTAINER/EXEC",
		Short: "Fetch the logs of a container",
		Long:  `Fetch the logs of a container`,
		Args:  cobra.MinimumNArgs(1),
		Run:   logHandle,
	}

	logCmd.Flags().BoolVar(&follow, "follow", false, "Follow log output")
	logCmd.Flags().BoolVarP(&timestamps, "timestamps", "t", false, "Show timestamps")
	logCmd.Flags().StringVar(&since, "since", "42m", "Show logs since timestamp")
	logCmd.Flags().Int32Var(&tail, "tail", 10, "Number of lines to show from the end of the logs")
	logCmd.Flags().BoolVarP(&exec, "exec", "e", false, "the id for container or exec instance")
	logCmd.Flags().BoolVar(&stdin, "stdin", false, "fetch stdin log")
	logCmd.Flags().BoolVar(&stdout, "stdout", false, "fetch stdout log")
	logCmd.Flags().BoolVar(&stderr, "stderr", false, "fetch stderr log")

	return logCmd
}

func logHandle(cmd *cobra.Command, args []string) {
	var id string
	if exec {
		id = args[0]
	} else {
		id = container.CheckNameOrId(args[0])
	}

	stream, err := connection.Client.FetchLog(context.Background(), &pb.FetchLogReq{
		Id:         id,
		Follow:     follow,
		Stdin:      stdin,
		Stdout:     stdout,
		Stderr:     stderr,
		Since:      0,
		Tail:       tail,
		Container:  !exec,
		Timestamps: timestamps,
	})

	if err != nil {
		fmt.Println(err)
		return
	}

	recv := func() (*pb.FetchLogResp, error) {
		if resp, err := stream.Recv(); err != nil {
			return nil, err
		} else {
			return resp, nil
		}
	}

	stdinChan := make(chan []*pb.LogEntry, 1)
	stdoutChan := make(chan []*pb.LogEntry, 1)
	stderrChan := make(chan []*pb.LogEntry, 1)
	exitChan := make(chan struct{})

	group := &sync.WaitGroup{}

	group.Add(1)
	go func() {
		defer func() {
			close(exitChan)
			group.Done()
		}()
		for {
			if resp, err := recv(); err != nil {
				fmt.Println(err)
				return
			} else {
				stdoutChan <- resp.LogStdoutEntry
				stderrChan <- resp.LogStderrEntry
			}
		}
	}()

	printStream := func(stdioChan chan []*pb.LogEntry) {
		for {
			select {
			case messages := <-stdioChan:
				for _, msg := range messages {
					if timestamps {
						_, _ = fmt.Fprint(os.Stderr, utils.FormatTimeStamps(msg.Time), "\t", msg.Line)
					} else {
						_, _ = fmt.Fprint(os.Stderr, msg.Line)
					}
				}
			case <-exitChan:
				return
			}
		}
	}

	group.Add(1)
	go func() {
		defer group.Done()
		printStream(stdinChan)
	}()

	group.Add(1)
	go func() {
		defer group.Done()
		printStream(stdoutChan)
	}()

	group.Add(1)
	go func() {
		defer group.Done()
		printStream(stderrChan)
	}()

	group.Wait()
}
